// SPDX-License-Identifier: GPL-2.0-or-later
/* Driver for Virtio QTBox device.
 *
 * Copyright 2022 HUAWEI TECHNOLOGIES CO., LTD.
 */

#include <linux/err.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include "uapi/virtio_qtbox.h"
#include "virtio_qtbox_comm.h"

#define filp2sandboxconsole(filp)	\
	container_of(filp->private_data, struct virtio_qtbox_sandbox_console, misc_dev)

static void console_dev_free(struct kref *kref)
{
	struct virtio_qtbox_sandbox_console *console_dev =
			container_of(kref, struct virtio_qtbox_sandbox_console, kref);

	kfree(console_dev->buf);
	kfree(console_dev);
}
void sandbox_console_write_local(struct virtio_qtbox_sandbox_instance *instance,
				char *log, uint32_t log_len)
{
	int i = 0, index;
	bool overwritten = false;
	struct virtio_qtbox_sandbox_console *console_dev = instance->console_dev;

	if (!console_dev || console_dev->dead)
		return;

	kref_get(&console_dev->kref);
	index = console_dev->head;
	while (i < log_len) {
		if (console_dev->dead)
			goto end;

		console_dev->buf[index] = log[i++];
		index = (index + 1) % CONSOLE_LOG_QUEUE_BUF_SIZE;
		if (index == console_dev->tail)
			overwritten = true;
	}

	mutex_lock(&console_dev->rw_mutex);
	console_dev->head = (console_dev->head + log_len) % CONSOLE_LOG_QUEUE_BUF_SIZE;
	if (overwritten) {
		pr_info("enclave(%llu)'s data buf tail has been overwritten!!!\n", instance->sid);
		console_dev->tail = (console_dev->head + 1) % CONSOLE_LOG_QUEUE_BUF_SIZE;
	}
	mutex_unlock(&console_dev->rw_mutex);
	wake_up_interruptible(&console_dev->wait);
 end:
	kref_put(&console_dev->kref, console_dev_free);
}

static int sandbox_console_open(struct inode *node, struct file *file)
{
	int ret = 0;
	struct virtio_qtbox_sandbox_console *console_dev = filp2sandboxconsole(file);

	if (!console_dev) {
		pr_err("no such device!");
		return -ENODEV;
	}

	ret = mutex_lock_interruptible(&console_dev->state_mutex);
	if (ret)
		return ret;

	if (console_dev->dead) {
		pr_err("device has been uninit");
		ret = -ENODEV;
		goto out;
	}

	if (console_dev->connected) {
		pr_err("instance(%llu) only support one client connect!", console_dev->sid);
		ret = -EBUSY;
		goto out;
	}
	console_dev->connected = true;

	kref_get(&console_dev->kref);
	pr_info("instance(%llu)'s sandbox console device open.\n", console_dev->sid);

 out:
	mutex_unlock(&console_dev->state_mutex);
	return ret;
}

static int sandbox_console_release(struct inode *inode, struct file *file)
{
	int ret;
	struct virtio_qtbox_sandbox_console *console_dev = filp2sandboxconsole(file);

	ret = mutex_lock_interruptible(&console_dev->state_mutex);
	if (ret)
		return ret;
	console_dev->connected = false;
	mutex_unlock(&console_dev->state_mutex);

	pr_info("instance(%llu)'s sandbox console device closed.\n", console_dev->sid);
	kref_put(&console_dev->kref, console_dev_free);
	return 0;
}

static __poll_t sandbox_console_poll(struct file *file, poll_table *wait)
{
	struct virtio_qtbox_sandbox_console *console_dev = filp2sandboxconsole(file);
	__poll_t mask;

	poll_wait(file, &console_dev->wait, wait);

	mask = console_dev->dead ? EPOLLHUP | EPOLLERR : EPOLLOUT | EPOLLWRNORM;
	if (console_dev->head != console_dev->tail) {
		mask |= EPOLLIN | EPOLLRDNORM;
	}

	return mask;
}


static bool sandbox_console_fetch_byte(struct virtio_qtbox_sandbox_console *console_dev, char *c)
{
	bool empty;

	empty = console_dev->head == console_dev->tail;
	if (!empty) {
		mutex_lock(&console_dev->rw_mutex);
		*c = console_dev->buf[console_dev->tail];
		console_dev->tail = (console_dev->tail + 1) % CONSOLE_LOG_QUEUE_BUF_SIZE;
		mutex_unlock(&console_dev->rw_mutex);
	}

	return !empty;
}

static ssize_t sandbox_console_read(struct file *file, char __user *buffer,
                                  size_t count, loff_t *ppos)
{
	struct virtio_qtbox_sandbox_console *console_dev = filp2sandboxconsole(file);
	char c;
	ssize_t read = 0;
	int error;
	for (;;) {
		if (console_dev->dead)
			return -ENODEV;

		if (console_dev->head == console_dev->tail &&
		    (file->f_flags & O_NONBLOCK))
			return -EAGAIN;

		if (count == 0)
			break;

		while (read < count && sandbox_console_fetch_byte(console_dev, &c)) {
			if (put_user(c, buffer++))
				return -EFAULT;
			read++;
		}

		if (read)
			break;

		if (!(file->f_flags & O_NONBLOCK)) {
			error = wait_event_interruptible(console_dev->wait,
					console_dev->head != console_dev->tail ||
					console_dev->dead);
			if (error)
				return error;
		}
	}
	return read;
}


static const struct file_operations sandbox_console_fops = {
	.owner		= THIS_MODULE,
	.open		= sandbox_console_open,
	.release	= sandbox_console_release,
	.poll		= sandbox_console_poll,
	.read		= sandbox_console_read,
	.llseek		= noop_llseek,
};


int virtio_qtbox_sandbox_console_init(struct virtio_qtbox_sandbox_instance *instance)
{
	struct virtio_qtbox_sandbox_console *console_dev;
	int ret = -1;

	console_dev = kzalloc(sizeof(struct virtio_qtbox_sandbox_console), GFP_KERNEL);
	if (!console_dev) {
		pr_err("can't allocate memory for a console device\n");
		return -ENOMEM;
	}

	kref_init(&console_dev->kref);
	if (snprintf(console_dev->name, sizeof(console_dev->name),
				 SANDBOX_LOG_NAME, instance->sid) < 0)
		goto err_free;
	console_dev->buf = kzalloc(CONSOLE_LOG_QUEUE_BUF_SIZE, GFP_KERNEL);
	if (!console_dev->buf) {
		pr_err("can't allocate memory for console_dev buf\n");
		ret = -ENOMEM;
		goto err_free;
	}
	console_dev->head = console_dev->tail = 0;
	console_dev->sid = instance->sid;
	init_waitqueue_head(&console_dev->wait);
	console_dev->dead = false;
	console_dev->connected = false;
	mutex_init(&console_dev->state_mutex);
	mutex_init(&console_dev->rw_mutex);

	console_dev->misc_dev.minor = MISC_DYNAMIC_MINOR;
	console_dev->misc_dev.name = console_dev->name;
	console_dev->misc_dev.fops = &sandbox_console_fops;
	console_dev->misc_dev.mode = 0660; /* file permission: rw-rw---- */

	ret = misc_register(&console_dev->misc_dev);
	if (ret < 0) {
		pr_err("Error in misc dev (%s) register [ret=%d]\n",
		       console_dev->name, ret);
		goto err_free;
	}

	pr_info("Success to init misc dev (%s)\n", console_dev->name);
	instance->console_dev = console_dev;
	return 0;

 err_free:
	kref_put(&console_dev->kref, console_dev_free);
	return ret;
}

int virtio_qtbox_sandbox_console_uninit(struct virtio_qtbox_sandbox_instance *instance)
{
	struct virtio_qtbox_sandbox_console *console_dev = instance->console_dev;

	mutex_lock(&console_dev->state_mutex);
	misc_deregister(&console_dev->misc_dev);
	console_dev->dead = true;
	mutex_unlock(&console_dev->state_mutex);

	wake_up_interruptible(&console_dev->wait);

	pr_info("Success to uninit misc dev (%s)\n", console_dev->name);
	kref_put(&console_dev->kref, console_dev_free);
	return 0;
}


static int virtio_qtbox_dataq_fill_one(struct virtqueue *vq,
				       struct log_msg *elem)
{
	struct scatterlist sg[1];
	int ret;

	sg_init_one(sg, elem, sizeof(*elem));

	ret = virtqueue_add_inbuf(vq, sg, 1, elem, GFP_KERNEL);
	if (!ret)
		ret = vq->num_free;

	return ret;
}

void virtio_qtbox_dataq_drain(struct virtio_qtbox *qtbox)
{
	struct log_msg *elem;
	int i;

	for (i = 0; i < qtbox->max_dataqs; i++) {
		if (qtbox->dataq[i].vq == NULL)
			continue;

		mutex_lock(&qtbox->dataq[i].dataq_mutex);

		while ((elem = virtqueue_detach_unused_buf(qtbox->dataq[i].vq)))
			kfree(elem);

		mutex_unlock(&qtbox->dataq[i].dataq_mutex);
	}
}

uint32_t virtio_qtbox_dataq_fill(struct virtio_qtbox_dataq *dataq)
{
	struct virtio_qtbox *qtbox;
	struct log_msg *elem;
	uint32_t nr_added = 0;
	int ret;

	if (dataq == NULL)
		return 0;

	qtbox = dataq->vq->vdev->priv;

	mutex_lock(&dataq->dataq_mutex);

	do {
		elem = kzalloc(sizeof(*elem), GFP_KERNEL);
		if (!elem)
			break;

		ret = virtio_qtbox_dataq_fill_one(dataq->vq, elem);
		if (ret < 0) {
			kfree(elem);
			break;
		}

		nr_added++;
	} while (ret > 0);

	mutex_unlock(&dataq->dataq_mutex);

	dev_info(&qtbox->vdev->dev, "add %u elements into dataq\n", nr_added);
	return nr_added;
}

static void virtio_qtbox_handle_data_message(struct virtio_qtbox *qtbox,
					     struct log_msg *elem)
{
	uint32_t sid = le32_to_cpu(elem->sid);
	uint32_t len = le32_to_cpu(elem->len);
	struct virtio_qtbox_sandbox_instance *instance;

	mutex_lock(&qtbox->sandbox.instances_mutex);

	list_for_each_entry(instance, &qtbox->sandbox.instances_list, node) {
		if (sid != cpu_to_le64(instance->sid))
			continue;

		sandbox_console_write_local(instance, elem->buf, len);
	}

	mutex_unlock(&qtbox->sandbox.instances_mutex);
}

void virtio_qtbox_data_work_handler(struct work_struct *work)
{
	struct log_msg *elem;
	struct virtio_qtbox *qtbox;
	unsigned int len;
	int ret;

	if (work == NULL)
		return;

	qtbox = container_of(work, struct virtio_qtbox, notify_dataq_work);

	mutex_lock(&qtbox->dataq[DATA_LOG_QUEUE].dataq_mutex);

	while ((elem = virtqueue_get_buf(qtbox->dataq[DATA_LOG_QUEUE].vq, &len))) {
		mutex_unlock(&qtbox->dataq[DATA_LOG_QUEUE].dataq_mutex);
		virtio_qtbox_handle_data_message(qtbox, elem);
		mutex_lock(&qtbox->dataq[DATA_LOG_QUEUE].dataq_mutex);
		ret = virtio_qtbox_dataq_fill_one(qtbox->dataq[DATA_LOG_QUEUE].vq, elem);
		if (ret < 0) {
			dev_warn(&qtbox->vdev->dev,
				 "failed to add one element into dataq, ret=%d\n", ret);
			kfree(elem);
		}
	}

	mutex_unlock(&qtbox->dataq[DATA_LOG_QUEUE].dataq_mutex);
}

void virtio_qtbox_data_cb(struct virtqueue *vq)
{
	struct virtio_qtbox *qtbox = vq->vdev->priv;

	queue_work(qtbox->data_wq, &qtbox->notify_dataq_work);
}
